package st.learnopengl.common

import gli_.gl
import gli_.gli
import glm_.mat4x4.Mat4
import glm_.quat.Quat
import glm_.vec2.Vec2i
import glm_.vec3.Vec3
import gln.ShaderType
import gln.TexParameter
import gln.TextureTarget
import gln.identifiers.GlProgram
import gln.identifiers.GlShader
import gln.identifiers.GlTexture
import gln.program.GlslProgram
import gln.texture.glGenTexture
import gln.texture.glGenerateMipmap2D
import gln.texture.glTexImage2D
import org.lwjgl.assimp.AIMatrix4x4
import org.lwjgl.assimp.AIQuaternion
import org.lwjgl.assimp.AIVector3D
import org.lwjgl.opengl.GL20
import java.lang.RuntimeException
import kotlin.math.acos
import kotlin.math.sin

object GLUT {
    /**
     * @param type - Shader Type, GL_VERTEX_SHADER or GL_FRAGMENT_SHADER
     * @param shaderFile - shader file path relative to resource root
     * @return shader handler
     */
    private fun createShader(type: ShaderType, shaderFile: String): GlShader {

        return GlShader.create(type).apply {
            source(Resource.loadText(shaderFile))
            compile()
            if (!compileStatus) {
                throw RuntimeException(infoLog)
            }
        }
    }

    /**
     * @param vertexShaderPath
     * @param fragmentShaderPath
     * @return program handle
     */
    fun createProgram(vertexShaderPath: String, fragmentShaderPath: String): GlProgram {
        val vs = createShader(ShaderType.VERTEX_SHADER, vertexShaderPath)
        val fs = createShader(ShaderType.FRAGMENT_SHADER, fragmentShaderPath)

        val ggo = GlProgram.create().apply {
            attach(vs)
            attach(fs)
            link()

            if (!linkStatus) {
                throw RuntimeException(infoLog)
            }
        }

        vs.delete()
        fs.delete()

        return ggo
    }

    /**
     * @param vertexShaderPath
     * @param fragmentShaderPath
     * @return program
     */
    fun createGlslProgram(vertexShaderPath: String, fragmentShaderPath: String): GlslProgram {
        return GlslProgram(Resource.loadText(vertexShaderPath), Resource.loadText(fragmentShaderPath))
    }

    /**
     * create program from pre created vertex and fragment shader
     * @param vs - vertex shader wrapper
     * @param fs - fragment shader wrapper
     * @return program wrapper
     */
    fun createProgram(vs: GlShader, fs: GlShader): GlProgram {

        return GlProgram.create().apply {
            attach(vs)
            attach(fs)
            link()

            if (!linkStatus) {
                throw RuntimeException(infoLog)
            }
        }
    }

    /**
     * load texture from file path
     * @param path - Texture path
     */
    fun loadTexture(path: String): GlTexture {
        val texture = gli.load(Resource.getResourceURL(path).toURI())
        println("width = ${texture.extent().x} height = ${texture.extent().y}")
        if (texture.empty()) {
            throw RuntimeException("Failed to load texture: $path")
        }
        gli.gl.profile = gl.Profile.GL33
        val (target, format) = gli.gl.translate(texture)
        assert(texture.format.isCompressed && target == gl.Target._2D)

        return GlTexture(glGenTexture()).bound(TextureTarget._2D) {
            parameter(TexParameter.WRAP_S, GL20.GL_REPEAT)
            parameter(TexParameter.WRAP_T, GL20.GL_REPEAT)
            parameter(TexParameter.MIN_FILTER, GL20.GL_LINEAR)
            parameter(TexParameter.MAG_FILTER, GL20.GL_LINEAR)
            glGenerateMipmap2D()

            glTexImage2D(format.internal, Vec2i(texture.extent()), format.external, format.type, texture.data())
        }
    }


    fun aiMatrixToMat4(aiMatrix4x4: AIMatrix4x4): Mat4 {
        val matrix = Mat4()

        matrix[0][0] = aiMatrix4x4.a1(); matrix[0][1] = aiMatrix4x4.b1(); matrix[0][2] = aiMatrix4x4.c1(); matrix[0][3] = aiMatrix4x4.d1()
        matrix[1][0] = aiMatrix4x4.a2(); matrix[1][1] = aiMatrix4x4.b2(); matrix[1][2] = aiMatrix4x4.c2(); matrix[1][3] = aiMatrix4x4.d2()
        matrix[2][0] = aiMatrix4x4.a3(); matrix[2][1] = aiMatrix4x4.b3(); matrix[2][2] = aiMatrix4x4.c3(); matrix[2][3] = aiMatrix4x4.d3()
        matrix[3][0] = aiMatrix4x4.a4(); matrix[3][1] = aiMatrix4x4.b4(); matrix[3][2] = aiMatrix4x4.c4(); matrix[3][3] = aiMatrix4x4.d4()

        return matrix;
    }

    fun aiVector3DToVec3(aiVector3D: AIVector3D): Vec3 {
        val vec3 = Vec3()
        vec3.x = aiVector3D.x()
        vec3.y = aiVector3D.y()
        vec3.z = aiVector3D.z()
        return vec3
    }

    fun aiQuaternionToQuat(aiQuaternion: AIQuaternion): Quat {
        return Quat(aiQuaternion.w(), aiQuaternion.x(), aiQuaternion.y(), aiQuaternion.z())
    }


    fun interpolateQuate(start: Quat, end: Quat, factor: Float): Quat {
        val out = Quat()

        var cosmo = start.x * end.x + start.y * end.y + start.z * end.z + start.w * end.w

        if (cosmo < 0.0f) {
            cosmo = -cosmo

            end.x = -end.x
            end.y = -end.y
            end.z = -end.z
            end.w = -end.w
        }

        var sclp = 0.0f
        var sclq = 0.0f

        if ((1.0f - cosmo) > 0.0001f) {
            val omega = acos(cosmo)
            val sinom = sin(omega)
            sclp = sin((1.0f - factor) * omega) / sinom
            sclq = sin(factor * omega) / sinom
        } else {
            sclp = 1.0f - factor
            sclq = factor
        }

        out.x = sclp * start.x + sclq * end.x
        out.y = sclp * start.y + sclq * end.y
        out.z = sclp * start.z + sclq * end.z
        out.w = sclp * start.w + sclq * end.w

        return out
    }
}